#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>

// swap is a singleton
static struct {
	FILE *fd;
	off_t size;
	int desc;
	size_t page_sz;
	void *munmap_buf;
	off_t munmap_offset; // absolute offset for the last get
	void *mmap_buf;
	off_t mmap_offset;   // the beg of already mmaped region
	int mmap_cnt;
	off_t offset;        // current offset
} swap;

int swap_init(off_t swap_sz, const char *path) {
	if ((swap.desc = open(path, O_CREAT | O_RDWR | O_TRUNC, 0777)) < 0)
		return 0;

	if (fallocate(swap.desc, 0, 0, swap_sz) < 0) {
		return 0;
	}
	swap.page_sz = 16 * getpagesize();
	swap.mmap_buf =
		mmap(NULL, swap.page_sz, PROT_READ | PROT_WRITE, MAP_SHARED, swap.desc, 0);

	swap.munmap_buf =
		mmap(NULL, swap.page_sz, PROT_READ | PROT_WRITE, MAP_SHARED, swap.desc, 0);

	if (swap.mmap_buf == MAP_FAILED)
		return 0;
	swap.offset = 0;
	swap.mmap_offset = 0;
	swap.munmap_offset = 0;
	swap.size = swap_sz;
	swap.mmap_cnt = 1;
	return 1;
}

off_t swap_in(void *addr, off_t size) {
	off_t offset = swap.offset;
	swap.offset += size; // absolute offset in file
	if (swap.offset > swap.size)
		return -1;
	// if not enough memory
	if (swap.mmap_offset + size > swap.page_sz) {
		munmap(swap.mmap_buf, swap.page_sz);
		swap.mmap_buf =
			mmap(NULL, swap.page_sz, PROT_READ | PROT_WRITE, MAP_SHARED, swap.desc, offset);
		swap.mmap_offset = 0;
		++swap.mmap_cnt;
	}
	memcpy(swap.mmap_buf + swap.mmap_offset, addr, size);
	swap.mmap_offset += size;
	return offset;
}

int swap_out(void *addr, off_t offset, off_t size) {
	if ((offset >= swap.mmap_cnt * swap.page_sz) &&
			(offset + size <= swap.mmap_cnt * (swap.page_sz + 1))) {
		memcpy(addr, offset - (swap.mmap_cnt * swap.page_sz) + swap.mmap_buf, size);
		return 1;
	}
	if ((offset >= swap.munmap_offset) &&
			(offset + size <= swap.munmap_offset + swap.page_sz)) {
		memcpy(addr, swap.munmap_buf + offset - swap.munmap_offset, size);
		return 1;
	}
	munmap(swap.munmap_buf, swap.page_sz);
	swap.munmap_buf =
		mmap(NULL, swap.page_sz, PROT_READ | PROT_WRITE, MAP_SHARED, swap.desc, offset);
	swap.munmap_offset = offset;
	memcpy(addr, swap.munmap_buf, size);
	return 1;
}

void swap_deinit() {
	if (swap.desc >= 0)
		close(swap.desc);
	swap.desc = -1;
	munmap(swap.mmap_buf, swap.page_sz);
}
